/**
 * 
 */
package com.jcode.netty.server;

import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jcode.netty.core.NetEvent;
import com.jcode.netty.core.NetEventType;
import com.jcode.netty.protocol.Packet;
import com.jcode.netty.util.RemotingHelper;

/**
 *
 * @Author dreajay
 * @DateTime 2016年12月1日 上午10:38:35
 * 
 */
public class ServerHandler extends ChannelDuplexHandler {
	private static Logger log = LoggerFactory.getLogger(ServerHandler.class);
	// channel>
	NettyServer server;

	public ServerHandler(NettyServer server) {
		this.server = server;
	}

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		server.processMessageReceived(ctx, (Packet) msg);
	}

	/**
	 * 一次生命周期可能不只会执行一次
	 */
	@Override
	public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
		final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel());
		log.info("NETTY SERVER PIPELINE: channelRegistered {}", remoteAddress);
		super.channelRegistered(ctx);
	}

	/**
	 * 一次生命周期可能不只会执行一次
	 */
	@Override
	public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
		final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel());
		log.info("NETTY SERVER PIPELINE: channelUnregistered, the channel[{}]", remoteAddress);
		super.channelUnregistered(ctx);
	}

	/**
	 * 一次生命周期只会执行一次
	 */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel());
		log.info("NETTY SERVER PIPELINE: channelActive, the channel[{}]-{}", remoteAddress, ctx.channel().id().asShortText());
		super.channelActive(ctx);
		server.getConnectionManager().addConnect(ctx.channel());
		server.fireNetEvent(new NetEvent(NetEventType.CONNECT, remoteAddress, ctx.channel()));
	}

	/**
	 * 一次生命周期只会执行一次
	 */
	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel());
		log.info("NETTY SERVER PIPELINE: channelInactive, the channel[{}]", remoteAddress);
		super.channelInactive(ctx);
		server.getConnectionManager().closeConnect(ctx.channel());
		server.fireNetEvent(new NetEvent(NetEventType.CLOSE, remoteAddress, ctx.channel()));
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel());
		log.warn("NETTY SERVER PIPELINE: exceptionCaught {} {}", remoteAddress, cause.getClass().getCanonicalName(), cause);
		server.getConnectionManager().closeConnect(ctx.channel());
		server.fireNetEvent(new NetEvent(NetEventType.EXCEPTION, remoteAddress, ctx.channel()));
	}

	/**
	 * <pre>
	 * 读写操作空闲时会触发一个IdleStateEvent事件，并回调此方法。
	 * 如果需要监听IdleStateEvent事件，需要在注册一个IdleStateHandler：
	 * channel.pipeline().addLast("idleStateHandler", new IdleStateHandler(60, 30, 0));
	 * 参考：http://www.mamicode.com/info-detail-903786.html
	 * 
	 * 
	 * 心跳机制：
	 * 
	 *    1. 心跳包之所以叫心跳包是因为：它像心跳一样每隔固定时间发一次，以此来告诉服务器，这个客户端还活着。事实上这是为了保持长连接，至于这个包的内容，是没有什么特别规定的，不过一般都是很小的包，或者只包含包头的一个空包。
	 *    
	 *    2. 在TCP的机制里面，本身是存在有心跳包的机制的，也就是TCP的选项：SO_KEEPALIVE。系统默认是设置的2小时的心跳频率。但是它检查不到机器断电、网线拔出、防火墙这些断线。而且逻辑层处理断线可能也不是那么好处理。一般，如果只是用于保活还是可以的。
	 *     
	 *    3. 心跳包一般来说都是在逻辑层发送空的echo包来实现的。下一个定时器，在一定时间间隔下发送一个空包给客户端，然后客户端反馈一个同样的空包回来，服务器如果在一定时间内收不到客户端发送过来的反馈包，那就只有认定说掉线了。
	 *    
	 *    4. 其实，要判定掉线，只需要send或者recv一下，如果结果为零，则为掉线。但是，在长连接下，有可能很长一段时间都没有数据往来。理论上说，这个连接是一直保持连接的，但是实际情况中，如果中间节点出现什么故障是难以知道的。更要命的是，有的节点（防火墙）会自动把一定时间之内没有数据交互的连接给断掉。在这个时候，就需要我们的心跳包了，用于维持长连接，保活。
	 *    
	 *    5. 在获知了断线之后，服务器逻辑可能需要做一些事情，比如断线后的数据清理呀，重新连接呀……当然，这个自然是要由逻辑层根据需求去做了。
	 *    
	 *    6. 总的来说，心跳包主要也就是用于长连接的保活和断线处理。一般的应用下，判定时间在30-40秒比较不错。如果实在要求高，那就在6-9秒。
	 *  
	 * 
	 *    如果在指定时间server没有收到  那么 上面那个重写的方法 就会断开应该是read_idle超时。netty3中IdleStateAwareChannelHandler 实际就是对上下行handle的一个包装 没有什么神奇的地方
	 * </pre>
	 */
	@Override
	public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
		if (evt instanceof IdleStateEvent) {
			IdleStateEvent evnet = (IdleStateEvent) evt;
			// READER_IDLE \ WRITER_IDLE \ ALL_IDLE
			if (evnet.state().equals(IdleState.ALL_IDLE)) {
				final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(ctx.channel());
				log.error("NETTY SERVER PIPELINE: IDLE exception [{}]", remoteAddress);
				server.getConnectionManager().closeConnect(ctx.channel());
				server.fireNetEvent(new NetEvent(NetEventType.ALL_IDLE, remoteAddress, ctx.channel()));
			}
		}
		ctx.fireUserEventTriggered(evt);
	}

}
